﻿#include "../inc/ff_utils_d3d.h"
#include "d3dUtil.h"

using Microsoft::WRL::ComPtr;
using namespace std;
using namespace DirectX;

namespace feifei
{
	// -----------------------------------------------------------------------------
	static bool D3DInitFlag = false;
	static Microsoft::WRL::ComPtr<IDXGIFactory4> DXGIFFactory;	// 工厂
	static Microsoft::WRL::ComPtr<ID3D12Device> D3DDevice;		// DX设备,代表一个显示适配器
	Microsoft::WRL::ComPtr<ID3D12CommandAllocator> DirectCmdListAlloc;	// 命令分配器
	static Microsoft::WRL::ComPtr<ID3D12Fence> Fence;				// 围栏
	// 描述符(视图)相关 -------------------------------------------------------------
	static UINT RtvDescriptorSize = 0;		// 渲染目标视图大小
	static UINT DsvDescriptorSize = 0;		// 深度/模板视图大小
	static UINT CbvSrvUavDescriptorSize = 0;
	// -----------------------------------------------------------------------------

	DXGI_FORMAT mBackBufferFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
	DXGI_FORMAT mDepthStencilFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
	void LogOutputDisplayModes(IDXGIOutput* output, DXGI_FORMAT format)
	{
		UINT count = 0;
		UINT flags = 0;

		// Call with nullptr to get list count.
		output->GetDisplayModeList(format, flags, &count, nullptr);

		std::vector<DXGI_MODE_DESC> modeList(count);
		output->GetDisplayModeList(format, flags, &count, &modeList[0]);

		for (auto& x : modeList)
		{
			UINT n = x.RefreshRate.Numerator;
			UINT d = x.RefreshRate.Denominator;
			std::wstring text =
				L"Width = " + std::to_wstring(x.Width) + L" " +
				L"Height = " + std::to_wstring(x.Height) + L" " +
				L"Refresh = " + std::to_wstring(n) + L"/" + std::to_wstring(d) +
				L"\n";

			::OutputDebugString(text.c_str());
		}
	}
	void LogAdapterOutputs(IDXGIAdapter* adapter)
	{
		UINT i = 0;
		IDXGIOutput* output = nullptr;
		while (adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND)
		{
			DXGI_OUTPUT_DESC desc;
			output->GetDesc(&desc);

			std::wstring text = L"***Output: ";
			text += desc.DeviceName;
			text += L"\n";
			OutputDebugString(text.c_str());

			LogOutputDisplayModes(output, mBackBufferFormat);

			ReleaseCom(output);

			++i;
		}
	}
	void LogAdapters()
	{
		UINT i = 0;
		IDXGIAdapter* adapter = nullptr;
		std::vector<IDXGIAdapter*> adapterList;
		while (DXGIFFactory->EnumAdapters(i, &adapter) != DXGI_ERROR_NOT_FOUND)
		{
			DXGI_ADAPTER_DESC desc;
			adapter->GetDesc(&desc);

			std::wstring text = L"***Adapter: ";
			text += desc.Description;
			text += L"\n";

			OutputDebugString(text.c_str());

			adapterList.push_back(adapter);

			++i;
		}

		for (size_t i = 0; i < adapterList.size(); ++i)
		{
			LogAdapterOutputs(adapterList[i]);
			ReleaseCom(adapterList[i]);
		}
	}

	E_ffState InitD3D()
	{
#if defined(DEBUG) || defined(_DEBUG) 
		// Enable the D3D12 debug layer.
		ComPtr<ID3D12Debug> debugController;
		ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)));
		debugController->EnableDebugLayer();
#endif
		// 1. 创建工厂
		CreateDXGIFactory1(IID_PPV_ARGS(&DXGIFFactory));
		// 2. 创建设备(默认设备)
		HRESULT hardwareResult = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&D3DDevice));
		// 3. 创建围栏
		D3DDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&Fence));
		// 4. 获取描述符大小(152)
		RtvDescriptorSize = D3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
		DsvDescriptorSize = D3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
		CbvSrvUavDescriptorSize = D3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
		// 5. 对4X MSAA质量级别的支持
		//m4xMsaaQuality = 1;
#ifdef _DEBUG
		LogAdapters();
#endif
		// 6. 创建命令队列
		//CreateCommandObjects();
		// 7. 创建交换链
		//CreateSwapChain();
		// 8. 创建描述符堆
		//CreateRtvAndDsvDescriptorHeaps();//基类只创建渲染和深度描述符堆，顶点和常量描述符由子类创建

		D3DInitFlag = true;
		return E_ffState::SUCCESS;
	}
	void ReleaseD3D()
	{
		//if (D3DDevice != nullptr)
			//FlushCommandQueue();
	}


	void D3DBase::CreateCommandObjects()
	{
		// 创建命令队列
		D3D12_COMMAND_QUEUE_DESC queueDesc = {};
		queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
		queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
		D3DDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue));

		// 创建命令分配器
		D3DDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&DirectCmdListAlloc));

		// 创建命令列表
		D3DDevice->CreateCommandList(
			0,
			D3D12_COMMAND_LIST_TYPE_DIRECT,
			DirectCmdListAlloc.Get(), // Associated command allocator
			nullptr,                   // Initial PipelineStateObject nullptr=空命令队列,用于初始化
			IID_PPV_ARGS(&mCommandList));

		// Start off in a closed state.  This is because the first time we refer 
		// to the command list we will Reset it, and it needs to be closed before
		// calling Reset.
		mCommandList->Close();
	}
	void D3DBase::CreateSwapChain()
	{
		// Release the previous swapchain we will be recreating.
		mSwapChain.Reset();

		DXGI_SWAP_CHAIN_DESC sd;
		sd.BufferDesc.Width = mWinWidth;
		sd.BufferDesc.Height = mWinHeight;
		sd.BufferDesc.RefreshRate.Numerator = 60;
		sd.BufferDesc.RefreshRate.Denominator = 1;
		sd.BufferDesc.Format = mBackBufferFormat;
		sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
		sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
		sd.SampleDesc.Count = 1; //  m4xMsaaState = false
		sd.SampleDesc.Quality = 0;//  m4xMsaaState = false
		sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 用于渲染目标
		sd.BufferCount = SwapChainBufferCount;
		sd.OutputWindow = mhWindow;
		sd.Windowed = true;
		sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
		sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

		// Note: Swap chain uses queue to perform flush.
		DXGIFFactory->CreateSwapChain(mCommandQueue.Get(), &sd, mSwapChain.GetAddressOf());
	}
	void D3DBase::CreateRtvAndDsvDescriptorHeaps()
	{
		// 渲染目标视图描述符堆
		D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc;
		rtvHeapDesc.NumDescriptors = SwapChainBufferCount;
		rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
		rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
		rtvHeapDesc.NodeMask = 0;
		D3DDevice->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&mRtvHeap));

		// 创建深度/模板视图描述符堆
		D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
		dsvHeapDesc.NumDescriptors = 1;
		dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
		dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
		dsvHeapDesc.NodeMask = 0;
		D3DDevice->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(&mDsvHeap));
	}
	bool D3DBase::Init()
	{
		if (D3DInitFlag == false)
			InitD3D();

		// 6. 创建队列
		CreateCommandObjects();
		// 7. 创建交换链
		CreateSwapChain();
		// 8. 创建描述符堆
		CreateRtvAndDsvDescriptorHeaps();//基类只创建渲染和深度描述符堆，顶点和常量描述符由子类创建
		
		OnResize();
		return true;
	}
	void D3DBase::Draw()
	{
		// Reuse the memory associated with command recording.
		// We can only reset when the associated command lists have finished execution on the GPU.
		ThrowIfFailed(DirectCmdListAlloc->Reset());

		// A command list can be reset after it has been added to the command queue via ExecuteCommandList.
		// Reusing the command list reuses memory.
		ThrowIfFailed(mCommandList->Reset(DirectCmdListAlloc.Get(), nullptr));

		// Indicate a state transition on the resource usage.
		mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));

		// Set the viewport and scissor rect.  This needs to be reset whenever the command list is reset.
		mCommandList->RSSetViewports(1, &mScreenViewport);
		mCommandList->RSSetScissorRects(1, &mScissorRect);

		// Clear the back buffer and depth buffer.
		mCommandList->ClearRenderTargetView(CurrentBackBufferView(), Colors::LightSteelBlue, 0, nullptr);
		mCommandList->ClearDepthStencilView(DepthStencilView(), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);

		// Specify the buffers we are going to render to.
		mCommandList->OMSetRenderTargets(1, &CurrentBackBufferView(), true, &DepthStencilView());

		// Indicate a state transition on the resource usage.
		mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));

		// Done recording commands.
		ThrowIfFailed(mCommandList->Close());

		// Add the command list to the queue for execution.
		ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
		mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

		// swap the back and front buffers
		ThrowIfFailed(mSwapChain->Present(0, 0));
		mCurrBackBuffer = (mCurrBackBuffer + 1) % SwapChainBufferCount;

		// Wait until frame commands are complete.  This waiting is inefficient and is
		// done for simplicity.  Later we will show how to organize our rendering code
		// so we do not have to wait per frame.
		FlushCommandQueue();
	}
	void D3DBase::OnResize()
	{
		// Flush before changing any resources.
		FlushCommandQueue();

		mCommandList->Reset(DirectCmdListAlloc.Get(), nullptr);

		// Release the previous resources we will be recreating.
		for (int i = 0; i < SwapChainBufferCount; ++i)
			mSwapChainBuffer[i].Reset();
		mDepthStencilBuffer.Reset();

		// 后台缓冲区，深度/模板缓冲区 的大小需要与矩形窗口相同
		// Resize the swap chain.
		ThrowIfFailed(mSwapChain->ResizeBuffers(
			SwapChainBufferCount,
			mWinWidth, mWinHeight,
			mBackBufferFormat,
			DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH));

		mCurrBackBuffer = 0;

		// 为每个交换链重建渲染目标视图(描述符)
		CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart()); // 获得描述符堆的第一个描述符
		for (UINT i = 0; i < SwapChainBufferCount; i++)
		{
			mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mSwapChainBuffer[i])); // 获得交换链的缓冲区,即用作渲染目标的资源
			D3DDevice->CreateRenderTargetView(mSwapChainBuffer[i].Get(), nullptr, rtvHeapHandle); // 为缓冲区创建渲染目标视图(描述符)
			rtvHeapHandle.Offset(1, RtvDescriptorSize);// 指向下一个描述符
		}

		// Create the depth/stencil buffer and view.
		// 创建深度/模板缓冲区(资源)
		D3D12_RESOURCE_DESC depthStencilDesc; // 资源描述符
		depthStencilDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
		depthStencilDesc.Alignment = 0;
		depthStencilDesc.Width = mWinWidth;
		depthStencilDesc.Height = mWinHeight;
		depthStencilDesc.DepthOrArraySize = 1;
		depthStencilDesc.MipLevels = 1;

		// Correction 11/12/2016: SSAO chapter requires an SRV to the depth buffer to read from 
		// the depth buffer.  Therefore, because we need to create two views to the same resource:
		//   1. SRV format: DXGI_FORMAT_R24_UNORM_X8_TYPELESS
		//   2. DSV Format: DXGI_FORMAT_D24_UNORM_S8_UINT
		// we need to create the depth buffer resource with a typeless format.  
		depthStencilDesc.Format = DXGI_FORMAT_R24G8_TYPELESS;

		depthStencilDesc.SampleDesc.Count = 1;//  m4xMsaaState = false
		depthStencilDesc.SampleDesc.Quality = 0;//  m4xMsaaState = false
		depthStencilDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
		depthStencilDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;

		D3D12_CLEAR_VALUE optClear;
		optClear.Format = mDepthStencilFormat;
		optClear.DepthStencil.Depth = 1.0f;
		optClear.DepthStencil.Stencil = 0;
		// 为深度/模板资源分配显存
		ThrowIfFailed(D3DDevice->CreateCommittedResource(
			&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), // 默认堆: GPU访问,CPU不访问
			D3D12_HEAP_FLAG_NONE,
			&depthStencilDesc,//资源描述符
			D3D12_RESOURCE_STATE_COMMON, // 资源的初始状态
			&optClear, // 清除资源的优化选项,若无优化,可填nullptr
			IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf()))); // 所创建的深度/模板资源

		// Create descriptor to mip level 0 of entire resource using the format of the resource.
		D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc;
		dsvDesc.Flags = D3D12_DSV_FLAG_NONE;
		dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
		dsvDesc.Format = mDepthStencilFormat;
		dsvDesc.Texture2D.MipSlice = 0;

		// 创建深度/模板资源描述符
		D3DDevice->CreateDepthStencilView(
			mDepthStencilBuffer.Get(), //深度/模板资源(显存)
			&dsvDesc, // 深度模板描述符
			DepthStencilView()); // 创建到深度模板资源描述符堆中

		// Transition the resource from its initial state to be used as a depth buffer.
		mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mDepthStencilBuffer.Get(),
			D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_DEPTH_WRITE));

		// Execute the resize commands.
		ThrowIfFailed(mCommandList->Close());
		ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
		mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists); // 将命令列表送入队列

		// Wait until resize is complete.
		FlushCommandQueue();

		// Update the viewport transform to cover the client area.
		// 配置视口结构体
		mScreenViewport.TopLeftX = 0;
		mScreenViewport.TopLeftY = 0;
		mScreenViewport.Width = static_cast<float>(mWinWidth);
		mScreenViewport.Height = static_cast<float>(mWinHeight);
		mScreenViewport.MinDepth = 0.0f;
		mScreenViewport.MaxDepth = 1.0f;

		// 配置剪裁矩形结构体
		mScissorRect = { 0, 0, mWinWidth, mWinHeight };
	}
	void D3DBase::FlushCommandQueue()
	{
		// Advance the fence value to mark commands up to this fence point.
		mCurrentFence++;

		// Add an instruction to the command queue to set a new fence point.  Because we 
		// are on the GPU timeline, the new fence point won't be set until the GPU finishes
		// processing all the commands prior to this Signal().
		mCommandQueue->Signal(Fence.Get(), mCurrentFence); // CPU端设置围栏

		// Wait until the GPU has completed commands up to this fence point.
		if (Fence->GetCompletedValue() < mCurrentFence)
		{
			HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);

			// Fire event when GPU hits current fence.  
			Fence->SetEventOnCompletion(mCurrentFence, eventHandle);

			// Wait until the GPU hits current fence event is fired.
			WaitForSingleObject(eventHandle, INFINITE); // CPU端等待GPU到达围栏
			CloseHandle(eventHandle);
		}
	}
	D3D12_CPU_DESCRIPTOR_HANDLE D3DBase::CurrentBackBufferView() const
	{
		return CD3DX12_CPU_DESCRIPTOR_HANDLE(
			mRtvHeap->GetCPUDescriptorHandleForHeapStart(),
			mCurrBackBuffer,
			RtvDescriptorSize);
	}

} // end namespace feifei
